﻿import * as sfs from "fs";
import * as q from 'q';
import { join, dirname, basename } from "path";
import * as crypto from 'crypto';
import { create } from "domain";
import { createHash } from "crypto";
import { AppContext } from "../appContext";
var mkdirp = require('mkdirp');

export class FS {
    truncate(path: string, len: number) {
        path = AppContext.map_path(path);
        return q.promise((resolve, reject) => {
            sfs.truncate(path, len, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }
    writeFile(path: string, data: string, options: any) {
        path = AppContext.map_path(path);
        return q.promise((resolve, reject) => {
            sfs.writeFile(path, data, options, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }
    readDirsInDir(path: string) {
        path = AppContext.map_path(path);
        return q.promise((resolve, reject) => {
            sfs.readdir(path, (err, files) => {
                if (err) {
                    reject(err);
                    return;
                }
                var directoies = files.map(file => {
                    var fileName = join(path, file);
                    var stats = sfs.statSync(fileName);
                    this.wrap(stats, path, file);
                    return stats;
                }).filter(file => file.isDirectory());
                resolve(directoies);
            });
        });
    }
    readdir(path: string) {
        path = AppContext.map_path(path);
        return q.promise((resolve, reject) => {
            sfs.readdir(path, (err, files) => {
                if (err) {
                    reject(err);
                    return;
                }
                var directoies = files.map(file => {
                    var fileName = join(path, file);
                    var stats = sfs.statSync(fileName);
                    this.wrap(stats, path, file);
                    return stats;
                });
                resolve(directoies);
            });
        });
    }
    etag(size: number, mtime: Date) {
        return crypto.createHash("sha1")
            .update(size.toLocaleString() + mtime.getTime().toLocaleString())
            .digest("hex");
    }
    wrap(stats: sfs.Stats, path: string, file: string) {
        stats["atimestamp"] = stats.atime.getTime();
        stats["birthtimestamp"] = stats.birthtime.getTime();
        stats["mtimestamp"] = stats.mtime.getTime();
        stats["directory"] = stats.isDirectory();
        stats["blockDevice"] = stats.isBlockDevice();
        stats["characterDevice"] = stats.isCharacterDevice();
        stats["fifo"] = stats.isFIFO();
        stats["file"] = stats.isFile();
        stats["socket"] = stats.isSocket();
        stats["symbolicLink"] = stats.isSymbolicLink();
        stats["etag"] = this.etag(stats.size, stats.mtime);
        stats["fullName"] = join(path, file);
        stats["name"] = file;
    }
    stat(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.stat(path, (err, stats) => {
                if (err) {
                    reject(err);
                    return;
                }
                this.wrap(stats, path, basename(path));
                resolve(stats);
            });
        });
    }
    lstat(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.lstat(path, (err, stats) => {
                if (err) {
                    reject(err);
                    return;
                }
                this.wrap(stats, dirname(path), basename(path));
                resolve(stats);
            });
        });
    }
    upload(file) {
        return q.Promise((resolve, reject) => {
            mkdirp.sync(file.path);
            if (file.isDirectory) {
                sfs.mkdirSync(join(file.path, file.name));
                resolve();
                return;
            }
            var fileBuffer = new Buffer(new Uint8Array(file.data));
            sfs.writeFile(join(file.path, file.name), fileBuffer, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }
    detectEncodingByBOMFromBuffer(buffer: NodeBuffer, bytesRead: number): string {
        if (!buffer || bytesRead < 2) {
            return null;
        }

        const b0 = buffer.readUInt8(0);
        const b1 = buffer.readUInt8(1);

        // UTF-16 BE
        if (b0 === 0xFE && b1 === 0xFF) {
            return 'utf16be';
        }

        // UTF-16 LE
        if (b0 === 0xFF && b1 === 0xFE) {
            return 'utf16le';
        }

        if (bytesRead < 3) {
            return null;
        }

        const b2 = buffer.readUInt8(2);

        // UTF-8
        if (b0 === 0xEF && b1 === 0xBB && b2 === 0xBF) {
            return 'utf8';
        }

        return null;
    }
    isTextFile(buffer: Buffer) {
        let enc = this.detectEncodingByBOMFromBuffer(buffer, buffer.byteLength);

        // Detect 0 bytes to see if file is binary (ignore for UTF 16 though)
        let isText = true;
        if (enc !== 'utf16be' && enc !== 'utf16le') {
            for (let i = 0; i < buffer.byteLength && i < 512; i++) {
                if (buffer.readInt8(i) === 0) {
                    return false;
                }
            }
        }
        return true;
    }
    readBinaryFile(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.readFile(path, (err, buffer) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(buffer.toString("base64"));
            });
        });
    }
    readFile(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.readFile(path, (err, buffer) => {
                if (err) {
                    reject(err);
                    return;
                }
                if (this.isTextFile(buffer)) {
                    resolve(buffer.toString("utf8"));
                    return;
                }
                reject({
                    fileOperationResult: 0
                });
            });
        });
    }
    exists(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.exists(path, (data) => {
                resolve(data);
            });
        });
    }
    chmod(path: string, mode: number) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.chmod(path, mode, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }
    mkdir(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.mkdir(path, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve({
                    success: true
                });
            });
        });
    }
    realpath(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.realpath(path, (err, rpath) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(rpath);
            });
        });
    }
    access(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.access(path, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }
    open(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.open(path, 'a', null, (err, fd) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(fd);
            });
        });
    }
    futimes(fd: number, atimestamp: number, mtimestamp: number) {
        return q.Promise((resolve, reject) => {
            sfs.futimes(fd, new Date(atimestamp), new Date(mtimestamp), (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }

    unlink(path) {
        return q.Promise((resolve, reject) => {
            sfs.unlink(path, err => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            })
        });
    }

    rename(source: string, target: string) {
        source = AppContext.map_path(source);
        target = AppContext.map_path(target);
        return q.Promise((resolve, reject) => {
            sfs.rename(source, target, err => {
                err ? reject(err) : resolve(null);
            })
        });
    }

    close(fd: number) {
        return q.Promise((resolve, reject) => {
            sfs.close(fd, err => {
                err ? reject(err) : resolve(null);
            })
        });
    }

    rmdir(path: string) {
        path = AppContext.map_path(path);
        return q.Promise((resolve, reject) => {
            sfs.rmdir(path, err => {
                err ? reject(err) : resolve();
            });
        });
    }

    copyFile(source: string, target: string, mode: number) {
        source = AppContext.map_path(source);
        target = AppContext.map_path(target);
        return q.Promise((resolve, reject) => {
            const reader = sfs.createReadStream(source);
            const writer = sfs.createWriteStream(target, { mode });

            let finished = false;
            const finish = (error?: Error) => {
                if (!finished) {
                    finished = true;

                    // in error cases, pass to callback
                    if (error) {
                        reject(error);
                    }

                    // we need to explicitly chmod because of https://github.com/nodejs/node/issues/1104
                    else {
                        sfs.chmod(target, mode, (err) => {
                            if (err) {
                                reject(err);
                            }
                            resolve();
                        });
                    }
                }
            };

            // handle errors properly
            reader.once('error', error => finish(error));
            writer.once('error', error => finish(error));

            // we are done (underlying fd has been closed)
            writer.once('close', () => finish());

            // start piping
            reader.pipe(writer);
        });
    }
}